import 'dart:collection';
import 'dart:io';

import 'package:networking/networking.dart';
import 'package:networking/src/Interceptor/nw_logging_Interceptor.dart';
import 'package:networking/src/adapter/nw_adapter.dart';

class NWCore extends NWAdapter {
  final HashMap<String, Dio> _idol = HashMap();
  bool refreshingToken = false;

  Future<Result> send<T>(NWRequest<T> request) async {
    if (refreshingToken) {
      var errCode = NWErrorCode.refreshingToken;
      var err = NWError(errCode, null, "refreshingToken");
      return Result.failed(err);
    }
    if (request.isRefreshToken) {
      refreshingToken = request.isRefreshToken;
    }
    List<NWInject> requestInjects = [];
    List<NWInject> responseInjects = [];
    List<NWInject> errorInjects = [];
    if (request.injects != null && (request.injects?.isNotEmpty ?? false)) {
      for (var inject in request.injects!) {
        switch (inject.tense) {
          case NWInjectTense.request:
            requestInjects.add(inject);
            break;
          case NWInjectTense.response:
            responseInjects.add(inject);
            break;
          case NWInjectTense.error:
            errorInjects.add(inject);
            break;
        }
      }
    }

    var key =
        "${request.baseUrl()}${request.path()}/.${request.method.name.toLowerCase()}";
    var dio = _idol[key];
    if (dio == null) {
      dio = _createDio(request);
      _idol[key] = dio;
    } else {
      dio.options = _setOptions(request);
    }

    try {
      var response;
      switch (request.method) {
        case HTTPMethod.GET:
          response = await get(dio, request);
          break;
        case HTTPMethod.POST:
          response = await post(dio, request);
          break;
        case HTTPMethod.PUT:
          response = await put(dio, request);
          break;
        case HTTPMethod.DELETE:
          response = await delete(dio, request);
          break;
        case HTTPMethod.HEAD:
          response = await head(dio, request);
          break;
        default:
          var errCode = NWErrorCode.network;
          var err = NWError(errCode, null, "HTTPMethod Empty");
          return Result.failed(err);
      }
      refreshingToken = false;
      return Result.successful(response);
    } catch (e) {
      refreshingToken = false;
      try {
        for (var inject in errorInjects) {
          if (inject.requirement()) {
            inject.replay = false;
            final result = await inject.execute(e);
            switch (result.e) {
              case ResultEnum.success:
                return send(request);
              default:
                return NWCoreHandleException.handle(e);
            }
          }
        }
        return NWCoreHandleException.handle(e);
      } catch (e) {
        return NWCoreHandleException.handle(e);
      }
    }
  }

  Dio _createDio(NWRequest request) {
    final options = _setOptions(request);
    Dio dio = Dio(options);
    var interceptors = request.interceptors();
    if (interceptors != null && interceptors.isNotEmpty) {
      dio.interceptors.addAll(interceptors);
    }
    dio.interceptors.add(NWLogginInterceptor());
    return dio;
  }

  BaseOptions _setOptions(NWRequest request) {
    var options = BaseOptions(
      baseUrl: request.baseUrl(),
      connectTimeout: request.timeout,
      receiveTimeout: request.timeout,
      headers: request.headers,
      responseDecoder: request.responseDecoder,
    );
    return options;
  }

  @override
  post(Dio dio, NWRequest request) async {
    if (request.file != null) {
      String fileName = request.file!.path.split('/').last;
      request.addBody('file',
          await MultipartFile.fromFile(request.file!.path, filename: fileName));
      FormData formData = FormData.fromMap(request.body);
      var result = await dio.post(
        request.path(),
        cancelToken: request.cancelToken,
        queryParameters: request.query.isNotEmpty ? request.query : null,
        data: formData,
        onSendProgress: request.onSendProgress,
        onReceiveProgress: request.onReceiveProgress,
      );
      return result;
    } else {
      var result = await dio.post(
        request.path(),
        cancelToken: request.cancelToken,
        queryParameters: request.query.isNotEmpty ? request.query : null,
        data: request.body.isNotEmpty ? request.body : null,
      );
      return result;
    }
  }

  @override
  get(Dio dio, NWRequest request) async {
    var result = await dio.get(
      request.path(),
      cancelToken: request.cancelToken,
      queryParameters: request.query.isNotEmpty ? request.query : null,
      data: request.body.isNotEmpty ? request.body : null,
    );
    return result;
  }

  @override
  put(Dio dio, NWRequest request) async {
    var result = await dio.put(
      request.path(),
      cancelToken: request.cancelToken,
      queryParameters: request.query.isNotEmpty ? request.query : null,
      data: request.body.isNotEmpty ? request.body : null,
    );
    return result;
  }

  @override
  delete(Dio dio, NWRequest request) async {
    var result = await dio.delete(
      request.path(),
      cancelToken: request.cancelToken,
      queryParameters: request.query.isNotEmpty ? request.query : null,
      data: request.body.isNotEmpty ? request.body : null,
    );
    return result;
  }

  @override
  head(Dio dio, NWRequest request) async {
    var result = await dio.head(
      request.path(),
      cancelToken: request.cancelToken,
      queryParameters: request.query.isNotEmpty ? request.query : null,
      data: request.body.isNotEmpty ? request.body : null,
    );
    return result;
  }
}

class NWCoreHandleException {
  static Result handle(Object exception) {
    if (exception is DioException) {
      DioException dioErr = exception;
      if (dioErr.error != null) {
        var socketErr = dioErr.error as SocketException;
        var errCode = NWErrorCode.network;
        var err = NWError(errCode, null, socketErr.message, error: dioErr);
        return Result.failed(err);
      } else if (dioErr.response == null) {
        var errCode = NWErrorCode.network;
        var err = NWError(errCode, null, dioErr.message ?? "Response Empty",
            error: dioErr);
        return Result.failed(err);
      } else {
        var httpCode = HttpCode(dioErr.response?.statusCode ?? 0,
            dioErr.response?.statusMessage ?? "Message Empty");
        var errCode = NWErrorCode.network;
        var err = NWError(
            errCode, httpCode, dioErr.message ?? httpCode.describe,
            error: dioErr);
        return Result.failed(err);
      }
    } else {
      var err = NWError(NWErrorCode.unknown, null, "$exception");
      return Result.failed(err);
    }
  }
}
